--- import { type CollectionEntry, getCollection } from "astro:content"; import { render } from "astro:content"; import Translations from "@components/Translations.astro"; import { toIso8601Full } from "@utils/datetime"; import ReadingTime from "@components/ReadingTime.astro"; import Keywords from "@components/Keywords.astro"; import Citations from "@components/Citations.astro"; import Signature from "@components/signature/Signature.astro"; import CopyrightNotice from "@components/CopyrightNotice.astro"; import { verifier as verifierPrototype } from "@lib/pgp/verify"; import { getSigners, isTranslation } from "@lib/collection/helpers"; import { get } from "@utils/anonymous"; import Authors from "@components/signature/Authors.astro"; import { getEntry } from "astro:content"; import Base from "@layouts/Base.astro"; import readingTime from "reading-time"; import type { Entry, MicroEntry, OriginalEntry, } from "@lib/collection/schemas"; export async function getStaticPaths() { const posts = await getCollection("blog"); return posts.map((post) => ({ params: { slug: post.id }, props: post, })); } type Props = CollectionEntry<"blog">; const post = Astro.props; let original: OriginalEntry | MicroEntry; if (isTranslation(post)) { original = await getEntry(post.data.translationOf) as | OriginalEntry | MicroEntry; if (!original) { throw new Error(`Original post not found for ${post.id}`); } const originalAuthor = (original.data.signers ?? []).filter( (s) => s.role === "author", ).map((s) => s.entity.id)?.[0]; const originalCoAuthors = new Set( (original.data.signers ?? []).filter( (s) => s.role === "co-author", ).map((s) => s.entity.id), ); const translationAuthor = (post.data.signers ?? []).filter( (s) => s.role === "author", ).map((s) => s.entity.id)?.[0]; const translationCoAuthors = new Set( (post.data.signers ?? []).filter( (s) => s.role === "co-author", ).map((s) => s.entity.id), ); if ( (translationAuthor !== undefined && translationAuthor !== originalAuthor) || !translationCoAuthors.isSubsetOf(originalCoAuthors) ) { throw new Error( `Post ${post.id} has mismatched (co-)authors from original post ${original.id}`, ); } const translators = (post.data.signers ?? []).filter( (s) => s.role === "translator", ).map((s) => s.entity.id); for (const translator of translators) { if ( originalAuthor === translator || originalCoAuthors.has(translator) ) { throw new Error( `Translator ${translator} in ${post.id} is already a (co-)author in original post`, ); } } } else { original = post; if (post.data.signers?.some((x) => x.role === "translator")) { throw new Error( `Post ${post.id} is not a translation but has translators defined`, ); } } // Add own post as a translation const translationsSet = new Set( (await getCollection( "blog", (x) => (x.data.kind === "translation") && x.data.translationOf.id === (post.data.kind === "translation" ? post.data.translationOf.id : post.id), ) ?? []).map(({ id }) => id), ); translationsSet.add(post.id); const translations = [...translationsSet.values()].map((id) => ({ collection: post.collection, id, })); const signers = await getSigners(post); const verifier = await verifierPrototype.then((x) => x.clone()); // Add signers public keys to keyring for (const { data } of signers.map(get("entity"))) { if (data.publickey.armor !== undefined) { verifier.addKeyFromArmor(data.publickey.armor); } } const verification = post.filePath !== undefined ? await verifier.verify([ new URL(`file://${Deno.cwd()}/${post.filePath}`), ]) : undefined; const { Content } = await render(post); const { lang } = post.data; const commit = await verification?.commit; const reading = post.body ? readingTime(post.body, {}) : undefined; const minutes = reading === undefined ? undefined : Math.ceil(reading.minutes); const estimative = reading === undefined ? undefined : new Intl.DurationFormat(lang, { style: "long", }).format({ minutes }); const duration = minutes === undefined ? undefined : `PT${Math.floor(minutes / 60) > 0 ? Math.floor(minutes / 60) + "H" : ""}${ minutes % 60 > 0 ? minutes % 60 + "M" : "" }`; const getOrUndefined = (k: string) => k in post.data ? post.data[k as keyof typeof post.data] : undefined; const author = { "@type": "Person", } as const; const contributor = post.data.signers.filter(({ role }) => role === "co-author" ).map(() => { return { "@type": "Person", } as const; }); const translator = post.data.signers.filter(({ role }) => role === "translator" ).map(() => { return { "@type": "Person", } as const; }); const JSONLD = { "@context": "https://schema.org", "@type": "BlogPosting", "@id": Astro.url.href, articleBody: post.rendered?.html ?? post.body, abstract: getOrUndefined("description"), alternativeHeadline: getOrUndefined("subtitle"), author, citation: [].map(() => { return { "@type": "CreativeWork", }; }), contributor, copyrightHolder: [author, ...contributor, ...translator], // copyrightNotice: post.data.license, // WORKAROUND copyrightYear: post.data.dateCreated.getFullYear(), creativeWorkStatus: "Published", dateCreated: post.data.dateCreated.toISOString(), dateModified: "dateUpdated" in post.data ? post.data.dateUpdated?.toISOString() : undefined, // datePublished: undefined, // from git commit commit date encodingFormat: "text/html", headline: post.data.title, inLanguage: post.data.lang, isAccessibleForFree: true, isBasedOn: isTranslation(post) ? { "@type": "BlogPosting", "@id": new URL(`blog/read/${post.data.translationOf}`, Astro.site).href, } : undefined, keywords: original.data.keywords, license: post.data.license, // WORKAROUND locationCreated: { "@type": "Place", // XXX: getOrUndefined("locationCreated"), }, mentions: [].map(() => { return { "@type": "Thing", }; }), // publication: { // "@type": "PublicationEvent", // }, // from git commit // publisher: { // "@type": "Person", // }, // from git commit text: post.rendered?.html ?? post.body, timeRequired: post.body !== undefined ? duration : undefined, translationOf: isTranslation(post) ? { "@type": "BlogPosting", "@id": new URL(`blog/read/${post.data.translationOf}`, Astro.site).href, } : undefined, translator, // version: undefined // TODO wordCount: reading?.words, workTranslations: translations.filter(({ id }) => id !== post.id).map(( { id }, ) => ({ "@type": "BlogPosting", "@id": new URL(`blog/read/${id}`, Astro.site).href, })), description: getOrUndefined("description"), name: post.data.title, url: Astro.url.href, } as const; ---

{post.data.title}

{ "subtitle" in post.data && (

{post.data.subtitle}

) }
{ "description" in post.data && post.data.description && (

Resumo

{ post.data.description.split(new RegExp("\\s{2,}")) .map(( x, ) =>

{x}

) }
) } {verification && }
{ verification?.verifications && ( ) }
Data de criação
{ post.data.dateUpdated && (
Última atualização
) } { "locationCreated" in post.data && post.data.locationCreated && (
Local de criação
{post.data.locationCreated}
) }


{ "keywords" in original.data && ( ) } { "relatedPosts" in original.data && ( ) }